<script setup>
import { computed, reactive, watchEffect, ref } from 'vue'
import CodeReadme from './codeReadme.vue'
import { B2D, D2B } from './utils'
import preData from './pre'
import arrow from '../assets/arrow.svg'

// 配置，从外部配置
const config = reactive({
    addLen: preData[0].addLen, // 地址位数
    dataSize: preData[0].dataSize, // 内存中数据区域的长度
    codeStart: preData[0].codeStart, // 代码区域的开始位置
})

// 代码，从外部配置
const codes = ref(preData[0].codes)

// 预选项选择
const preSelect = ref(0)
const selectOk = () => {
    let index = preSelect.value
    config.addLen = preData[index].addLen // 地址位数
    config.dataSize = preData[index].dataSize // 内存中数据区域的长度
    config.codeStart = preData[index].codeStart // 代码区域的开始位置
    codes.value = preData[index].codes
}

// 代码数组
const codesArr = computed(() => {
    let arr = codes.value.split('\n')
    let res = []
    for (const it of arr) {
        console.log(it.trim())
        if (it.replace(/#.*/g, '').trim() === '') {
            continue
        }
        res.push(it.trim().split(' '))
    }
    return res
})

// ram
const ram = reactive(new Array(2 ** config.addLen).fill('0'))
// 地址位数发生变化时处理ram的内容
watchEffect(() => {
    let len = 2 ** config.addLen
    ram.length = len
})

// 寄存器
const regs = reactive({
    R0: 0,
    R1: 0,
    R2: 0,
    R3: 0,
})

// PC
const PC = ref(config.codeStart)

// 标志位G
const flagG = ref(0)

// 代码区开始位置发生变化时，把代码放到ram中
watchEffect(() => {
    PC.value = config.codeStart
    for (let i = 0; i < ram.length; i++) {
        if (
            i < config.codeStart ||
            i >= codesArr.value.length + config.codeStart
        ) {
            ram[i] = '0'
        } else {
            ram[i] = codesArr.value[i - config.codeStart]
                .join(' ')
                .replace(/#.*/g, '')
        }
    }
})

// 是否正在运行
const running = ref(false)

// 处理各种指令

// 是否正在等待用户输入
const inputing = ref(false)
// 输入框实例
const inputRef = ref(null)
// 输入确认按钮实例
const buttonRef = ref(null)
// 输入框值
const inputValue = ref('')
// 输出
const ioOutput = ref('')
// 状态信息
const statusMsg = ref('机器尚未运行')

// 关机
const end = () => {
    running.value = false
    statusMsg.value = `运行结束，已停机`
}

// 开机运行
const start = () => {
    running.value = true
    // 初始化内存
    for (let i = 0; i < config.dataSize; i++) {
        ram[i] = 0
    }
    // 初始化寄存器中的值
    regs['R0'] = regs['R1'] = regs['R2'] = regs['R3'] = 0
    // PC指向初始位置
    PC.value = config.codeStart
    // 把标志位G设置为0
    flagG.value = 0
    // 清空输出
    ioOutput.value = ''
    statusMsg.value = `运行中，即将运行指令 ${ram[PC.value]}`
}

// 验证一个寄存器名或者内存地址
const check = {
    reg(addr) {
        return ['R0', 'R1', 'R2', 'R3'].includes(addr)
    },
    ram(addr) {
        return B2D(addr) < ram.length
    },
    all(addr) {
        if (addr.length === 2) {
            return this.reg(addr)
        } else if (addr.length === config.addLen) {
            return this.ram(addr)
        } else {
            return false
        }
    },
}
// 指令处理函数集
const handlers = {
    mova: {
        run(curr) {
            // 把寄存器R2的值赋值给R1
            const name1 = curr[1]
            const name2 = curr[2]
            if (!check.reg(name1)) {
                return `Error：${name1} 不是一个有效的寄存器地址`
            }
            if (!check.reg(name2)) {
                return `Error：${name2} 不是一个有效的寄存器地址`
            }
            regs[name1] = regs[name2]
            return 'ok'
        },
    },
    movb: {
        run(curr) {
            // 把寄存器R2的值赋值给地址为R0的内存
            const name1 = curr[1]
            const name2 = curr[2]
            if (!check.reg(name1)) {
                return `Error：${name1} 不是一个有效的寄存器地址`
            }
            if (!check.reg(name2)) {
                return `Error：${name2} 不是一个有效的寄存器地址`
            }
            ram[regs[name1]] = regs[name2]
            return 'ok'
        },
    },
    movc: {
        run(curr) {
            // 把地址为R0的内存值赋值给R1
            const name1 = curr[1]
            const name2 = curr[2]
            if (!check.reg(name1)) {
                return `Error：${name1} 不是一个有效的寄存器地址`
            }
            if (!check.reg(name2)) {
                return `Error：${name2} 不是一个有效的寄存器地址`
            }
            regs[name1] = ram[regs[name2]]
            return 'ok'
        },
    },
    movd: {
        run(curr) {
            // 把PC的值复制给寄存器R3
            regs['R3'] = PC.value
            return 'ok'
        },
    },
    add: {
        run(curr) {
            let name1 = curr[1]
            let name2 = curr[2]
            if (!check.reg(name1)) {
                return `Error：${name1} 不是一个有效的寄存器地址`
            }
            if (!check.reg(name2)) {
                return `Error：${name2} 不是一个有效的寄存器地址`
            }
            regs[name1] = regs[name1] + regs[name2]
            return 'ok'
        },
    },
    sub: {
        run(curr) {
            let name1 = curr[1]
            let name2 = curr[2]

            if (!check.reg(name1)) {
                return `Error：${name1} 不是一个有效的寄存器地址`
            }
            if (!check.reg(name2)) {
                return `Error：${name2} 不是一个有效的寄存器地址`
            }

            let res = (regs[name1] = regs[name1] - regs[name2])
            if (res > 0) {
                flagG.value = 1
            } else {
                flagG.value = 0
            }
            return 'ok'
        },
    },
    jmp: {
        run(curr) {
            let name2 = curr[2]
            if (!check.reg(name2)) {
                return `Error：${name2} 不是一个有效的寄存器地址`
            }
            PC.value = regs[name2]
            return 'ok'
        },
    },
    jg: {
        run(curr) {
            if ((flagG.value >= 1)) {
                PC.value = regs['R3'] - 1
            }
            return 'ok'
        },
    },
    in: {
        async run(curr) {
            const name1 = curr[1]
            if(!check.reg(name1)) {
                return `Error：${name1} 不是一个有效的寄存器地址`
            }
            // 输入函数
            inputing.value = true
            inputRef.value.focus()
            statusMsg.value = '正在等待输入'
            return await new Promise((resolve, reject) => {
                const inputOk = (e) => {
                    if (e.type === 'keydown' && e.key !== 'Enter') {
                        return
                    }

                    let num = +inputValue.value

                    inputing.value = false
                    buttonRef.value.ref.removeEventListener('click', inputOk)
                    inputRef.value.ref.removeEventListener('keydown', inputOk)

                    if (isNaN(num)) {
                        resolve(
                            `Error：运行指令 in 时发生异常，输入的 ${inputValue.value} 不是一个数字`
                        )
                    }
                    regs[name1] = num
                    inputValue.value = ''
                    resolve('ok')
                }
                buttonRef.value.ref.addEventListener('click', inputOk)
                inputRef.value.ref.addEventListener('keydown', inputOk)
            })
        },
    },
    out: {
        run(curr) {
            let name = curr[1]
            if (!check.reg(name)) {
                return `Error：${name} 不是一个有效的寄存器地址`
            }
            ioOutput.value += regs[name]
            return 'ok'
        },
    },
    movi: {
        run(curr) {
            let val = +curr[1]
            if (isNaN(val)) {
                return `Error：运行指令 ${curr.join(' ')} 时发生异常，${
                    curr[1]
                } 不是一个数字`
            }
            regs['R0'] = val
            return 'ok'
        },
    },
    halt: {
        run() {
            end()
            return 'ok'
        },
    },

    5: {
        run(arr) {
            let sourse = arr[1]
            let target = arr[2]
            if (!check.all(sourse)) {
                return `Error：${sourse} 不是一个有效的存储地址`
            }
            if (!check.all(target)) {
                return `Error：${target} 不是一个有效的存储地址`
            }
            if (target.length === 2) {
                regs[target] = this.getVal(sourse)
            } else {
                ram[B2D(target)] = this.getVal(sourse)
            }
            return 'ok'
        },
        getVal(add) {
            if (add.length === 2) {
                return regs[add]
            } else {
                return ram[B2D(add)]
            }
        },
    },
}
// 下一步函数，执行当前指向的指令
const nextRun = async () => {
    // 机器未运行
    if (!running) {
        statusMsg.value = '机器尚未运行'
        return 'unrunning'
    }

    // 如果正在等待输入则什么都不做
    if (inputing.value) {
        statusMsg.value = '请先输入一个值'
        return 'inputing'
    }

    // 判断指令是否存在

    let curr = ram[PC.value].split(/ |,/g).map((it) => it.trim())
    console.log(curr)
    let res = ''
    if (handlers[curr[0]]) {
        res = await handlers[curr[0]].run(curr)
    } else {
        statusMsg.value = `找不到指令 ${ram[PC.value]}`
        return 'error'
    }

    // 执行指令是否成功
    if (res !== 'ok') {
        statusMsg.value = res
        return 'error'
    }

    PC.value++
    // PC的值超出ram的长度
    if (PC.value >= 2 ** config.addLen) {
        PC.value--
        end()
        return 'ok'
    }

    // 已停机
    if (!running.value) {
        return 'ok'
    }

    statusMsg.value = `即将运行指令 ${ram[PC.value]}`

    return 'ok'
}

// 一步到位
const oneStep = async () => {
    let res = ''
    while (res !== 'error' && running.value) {
        await new Promise((resolve, reject) => {
            setTimeout(async () => {
                res = await nextRun()
                resolve()
            }, 200)
        })
    }
}

const dialogVisible = ref(false)
const readmeVisible = ref(false)
</script>

<template>
    <el-dialog v-model="dialogVisible" title="配置">
        <div class="yxj-config-form">
            <el-form
                :disabled="running"
                label-position="top"
                label-width="120px"
            >
                <el-form-item label="预选">
                    <el-select v-model="preSelect" placeholder="请选择">
                        <el-option
                            :key="'pre' + i"
                            v-for="(v, i) in preData"
                            :label="v.label"
                            :value="i"
                        >
                        </el-option>
                    </el-select>
                    <el-button @click="selectOk" type="primary">加载</el-button>
                </el-form-item>
                <el-form-item label="内存位数">
                    <el-input-number
                        v-model="config.addLen"
                        :min="1"
                        :max="10"
                    />
                </el-form-item>
                <el-form-item label="数据段大小">
                    <el-input-number
                        v-model="config.dataSize"
                        :min="1"
                        :max="2 ** config.addLen - 1"
                    />
                </el-form-item>
                <el-form-item label="起始地址">
                    <el-select v-model="config.codeStart" placeholder="请选择">
                        <el-option
                            :key="'op' + i"
                            v-for="i in 2 ** config.addLen"
                            :label="D2B(i - 1, config.addLen)"
                            :value="i - 1"
                        >
                        </el-option>
                    </el-select>
                </el-form-item>
                <el-form-item label="指令">
                    <el-input
                        style="width: 100%"
                        v-model="codes"
                        resize="none"
                        :autosize="{ minRows: 10 }"
                        type="textarea"
                    />
                </el-form-item>
            </el-form>
        </div>
    </el-dialog>
    <div class="yxj-component">
        <div class="main-container">
            <div class="yxj">
                <div class="controls">
                    <el-button type="primary" @click="readmeVisible = true"
                        >查看指令说明</el-button
                    >
                    <el-button
                        :disabled="running"
                        type="primary"
                        @click="dialogVisible = true"
                        >显示配置</el-button
                    >
                    <el-button
                        :disabled="running"
                        type="primary"
                        @click="start"
                        >{{ running ? '运行中' : '开机' }}</el-button
                    >
                    <el-button
                        :disabled="!running"
                        type="primary"
                        @click="end"
                        >{{ running ? '停机' : '未运行' }}</el-button
                    >
                    <el-button
                        :disabled="!running || inputing"
                        type="primary"
                        @click="oneStep"
                        >运行到结束</el-button
                    >
                    <el-button
                        :disabled="!running || inputing"
                        type="primary"
                        @click="nextRun"
                        >下一步</el-button
                    >
                    <el-link
                        style="margin-left: 16px"
                        target="_blank"
                        href="https://gitee.com/aimaier4869/yxj-demo"
                        >源码地址</el-link
                    >
                </div>
                <div class="status-msg">{{ statusMsg }}</div>
                <div class="yxj-container">
                    <div class="container-left">
                        <div class="title">CPU</div>
                        <div class="cpu">
                            <div class="cpu-left">
                                <!-- 运算器 -->
                                <div class="alu">
                                    <div class="text">运算器</div>
                                </div>
                                <!-- 控制器 -->
                                <div class="plc">
                                    <div class="text">控制器</div>
                                    <div class="plc-val">
                                        初始值{{
                                            D2B(config.codeStart, config.addLen)
                                        }}
                                    </div>
                                </div>
                                <div class="flag-g">
                                    标志位G: {{ flagG }}
                                </div>
                            </div>
                            <div class="cpu-right">
                                <div class="reg reg1">
                                    <div class="text">
                                        寄存器R0
                                        <span class="value">{{
                                            regs['R0']
                                        }}</span>
                                    </div>
                                </div>
                                <div class="reg reg2">
                                    <div class="text">
                                        寄存器R1
                                        <span class="value">{{
                                            regs['R1']
                                        }}</span>
                                    </div>
                                </div>
                                <div class="reg reg3">
                                    <div class="text">
                                        寄存器R2
                                        <span class="value">{{
                                            regs['R2']
                                        }}</span>
                                    </div>
                                </div>
                                <div class="reg reg4">
                                    <div class="text">
                                        寄存器R3
                                        <span class="value">{{
                                            regs['R3']
                                        }}</span>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <div class="bottom-arrow">
                            <img :src="arrow" alt="" />
                        </div>
                        <div class="input">
                            <el-input
                                ref="inputRef"
                                @blur="
                                    () => {
                                        if (inputing) {
                                            inputRef.focus()
                                        }
                                    }
                                "
                                v-model="inputValue"
                                :disabled="!inputing"
                            ></el-input>
                            <el-button ref="buttonRef" :disabled="!inputing"
                                >确定</el-button
                            >
                        </div>
                    </div>
                    <div class="middle-arrow">
                        <img :src="arrow" alt="" />
                    </div>
                    <div class="container-right">
                        <div class="title">内存</div>
                        <div class="ram">
                            <div
                                class="ram-item"
                                :class="{
                                    'is-pc': i - 1 === PC,
                                    'data-ram': i - 1 < config.dataSize,
                                    'code-ram': i - 1 >= config.codeStart,
                                }"
                                :key="i"
                                v-for="i in 2 ** config.addLen"
                            >
                                <div class="ram-add">
                                    {{ D2B(i - 1, config.addLen) }}
                                </div>
                                <div class="ram-val">{{ ram[i - 1] }}</div>
                            </div>
                        </div>
                    </div>
                    <div class="input-output">
                        <!-- <div class="yxj-input">
                        <div class="io-label">输入</div>
                        <div class="io-val"></div>
                    </div> -->
                        <div class="yxj-output">
                            <div class="io-label">输出</div>
                            <div class="io-val">{{ ioOutput }}</div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <el-dialog
            title="指令说明"
            v-model="readmeVisible"
            style="width: 900px"
        >
            <CodeReadme />
        </el-dialog>
    </div>
</template>

<style scoped>
.yxj-component {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-bottom: 100px;
}

.main-container {
    display: flex;
    margin-bottom: 100px;
}

.yxj-config-form {
    padding-left: 40px;
}

.controls {
    padding: 16px;
}

.status-msg {
    padding: 16px;
    font-size: 24px;
}

.yxj-container {
    display: flex;
    width: 1100px;
    padding: 40px;
    border: 1px solid #000;
    font-family: '宋体';
}

.container-left,
.container-right {
    flex: 1;
}

.yxj-container .title {
    height: 100px;
    text-align: center;
    line-height: 100px;
    color: #00b0f0;
    font-size: 32px;
}

.cpu {
    display: flex;
    padding: 20px;
    border: 1px solid #494949;
    border-radius: 20px;
    box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1);
    background: linear-gradient(#fff, #f0f0f0);
}

.cpu-left {
    margin-right: 20px;
}

.alu,
.plc {
    width: 100px;
    padding: 10px;
    margin-bottom: 16px;
    border: 1px solid #494949;
    box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
    color: red;
    text-align: center;
    line-height: 40px;
    font-size: 20px;
}

.plc-val {
    font-size: 14px;
    line-height: 20px;
    color: #9200ff;
}

.reg {
    min-width: 140px;
    margin-bottom: 12px;
    height: 35px;
    border: 1px solid #494949;
    box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
}

.reg .text {
    font-size: 14px;
    line-height: 35px;
    color: red;
    padding: 0 10px;
    white-space: nowrap;
}

.reg .text .value {
    color: #000;
}

.ram-item {
    position: relative;
    display: flex;
    justify-content: center;
    height: 24px;
    line-height: 24px;
    font-size: 20px;
}

.ram-val {
    margin-left: 10px;
    padding: 0 10px;
    width: 150px;
    border: 1px solid #000;
    border-top: none;
}

.ram-item:first-child .ram-val {
    border-top: 1px solid #000;
}

.is-pc::before {
    display: block;
    content: '';
    position: absolute;
    left: 0px;
    width: 30px;
    height: 30px;
    background: url(../assets/arrow2.svg) left top/30px 30px no-repeat;
}

.data-ram .ram-val {
    background: lightgoldenrodyellow;
}
.code-ram .ram-val {
    background: lightblue;
}

.is-pc .ram-val {
    background: #70ad47;
}

.middle-arrow {
    padding: 150px 60px 0 40px;
    /* align-self: center; */
}

.middle-arrow img {
    width: 100px;
    height: 40px;
}

.bottom-arrow {
    display: flex;
    justify-content: center;
    padding: 20px 0;
}

.bottom-arrow img {
    transform: rotateZ(90deg);
    width: 50px;
    height: 50px;
}

.input {
    display: flex;
}

.input-output {
    width: 170px;
    margin-left: 16px;
}

.io-val {
    background: #000;
    color: #fff;
    margin: 10px 0 10px 8px;
    padding: 4px;
    font-size: 20px;
    min-height: 30px;
}
</style>
